using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace XLETL.ExceptionHandling
{
  /// <summary>
  /// Base ExceptionHandler class (implemented using the CodeProject article "Automatic Error Handling", Andy Searls)
  /// </summary>
	public class ExceptionHandler : HandlerBase
	{
		#region Static Entry Points
		public static void AddHandler( bool isConsoleApp )
		{
			_isConsoleApp = isConsoleApp;

			AddHandler( !_isConsoleApp, _isConsoleApp, _isConsoleApp );
		}

		public static void AddHandler( bool screenshot, bool showUser, bool kill )
		{
			_makeScreenshot = screenshot;
			_logToUI = showUser;
			_killAppOnException = kill;

			// attempt to load optional settings from .config file
			//LoadConfigSettings();

			// track the parent assembly that set up error handling
			// need to call this NOW so we set it appropriately; otherwise
			// we may get the wrong assembly at exception time!
			if( Assembly.GetEntryAssembly() == null )
				_parentAssembly = Assembly.GetCallingAssembly();
			else
				_parentAssembly = Assembly.GetEntryAssembly();

			// Attach handlers
			Application.ThreadException += new ThreadExceptionEventHandler( ThreadExceptionHandler );

			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler( UnhandledException );
		}

		public static void HandleException( Exception ex )
		{
			HandleException( string.Empty, ex );
		}

		public static void HandleException( string msg, Exception exceptn )
		{
			HandleException( msg, exceptn, _logToUI );
		}

		public static void HandleException( string msg, Exception ex, bool showToUI )
		{
			ExceptionHandler handler = new ExceptionHandler();

			handler.HandleExceptionInternal( msg, ex, showToUI );
		}
		#endregion

		#region Entry Point Worker Methods
		private void HandleExceptionInternal( string msg, Exception exceptn, bool showToUI )
		{
			Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;

			// Initialize properties
			_exceptionText = msg;

			if( !string.IsNullOrEmpty( _exceptionText ) )
			{
				_exceptionText += Environment.NewLine;
			}

			_exceptionType = string.Empty;

			_screenshotFullName = string.Empty;
			_logfileFullName = string.Empty;

			_logToScreenshotOK = false;
			_logToDatabaseOK = false;
			_logToFileOK = false;
			_logToEventLogOK = false;

			// turn the exception into an informative string
			try
			{
				_exceptionText += ExceptionToString( exceptn, true );
				_exceptionType = exceptn.GetType().FullName;
			}
			catch( Exception ex )
			{
				_exceptionText += "Error while generating exception string: " + ex.ToString();
				_exceptionType = ex.GetType().FullName;
			}

			if( _makeScreenshot )
			{
				try
				{
					TakeScreenshot();
					_exceptionText += Environment.NewLine + "Screenshot placed at:	" + _screenshotFullName;
				}
				catch( Exception ex )
				{
					_exceptionText += Environment.NewLine + "Screenshot Error:" + ExceptionToString( ex, true );
				}
			}

			Write( AppDomain.CurrentDomain.FriendlyName, "Application Error: ", new string[] { _screenshotFullName }, new Dictionary<string,Stream>() );

			Cursor.Current = System.Windows.Forms.Cursors.Default;

			// display message to the user
			if( showToUI )
			{
				try
				{
					ExceptionToUI();
				}
				catch
				{
					// Already logged other problems, can't attempt to log this for the risk of a closed loop
				}
			}
		}

		private static void ThreadExceptionHandler( object sender, System.Threading.ThreadExceptionEventArgs args )
		{
			HandleException( args.Exception );

			if( _killAppOnException )
			{
				Application.Exit();
			}
		}

		private static void UnhandledException( object sender, UnhandledExceptionEventArgs e )
		{
			HandleException( (Exception)e.ExceptionObject );

			if( _killAppOnException )
			{
				Application.Exit();
			}
		}
		#endregion

		#region Config Fields
		private static bool _makeScreenshot = false;
		private static bool _logToUI = false;
		private static bool _killAppOnException = false;
		private static bool _isConsoleApp = false;

		private static string _screenshotFileName = "ErrorScreen";
		private static ImageFormat _screenshotImageFormat = ImageFormat.Png;
		#endregion

		#region Exception Processing State Fields

		private string _screenshotFullName = string.Empty;
		private string _logfileFullName = string.Empty;

		private bool _logToScreenshotOK = false;
		#endregion

		#region Inputs
		#region Win32api screenshot calls
		// Windows API calls necessary to support screen capture
		[DllImport( "gdi32.dll" )]
		private static extern int BitBlt( int hDestDC, int x, int y, int nWidth, int nHeight, int hSrcDC, int xSrc, int ySrc, int dwRop );

		[DllImport( "user32.dll" )]
		private static extern int GetDC( int hwnd );

		[DllImport( "user32.dll" )]
		private static extern int ReleaseDC( int hwnd, int hdc );
		#endregion

		private void TakeScreenshot()
		{
			// note that screenshotname does not include the file type extension
			try
			{
				Rectangle screenRect = Screen.PrimaryScreen.Bounds;
				Bitmap screenBitmap = new Bitmap( screenRect.Width, screenRect.Height );
				Graphics screenGraphics = Graphics.FromImage( screenBitmap );

				// get a device context to the windows desktop and our destination  bitmaps
				int hdcSrc = GetDC( 0 );
				IntPtr hdcDest = screenGraphics.GetHdc();

				// copy what is on the desktop to the bitmap
				const int SRCCOPY = 0xCC0020;
				BitBlt( hdcDest.ToInt32(), 0, 0, screenRect.Width, screenRect.Height, hdcSrc, 0, 0, SRCCOPY );

				// release device contexts
				screenGraphics.ReleaseHdc( hdcDest );
				ReleaseDC( 0, hdcSrc );

				string extension = "." + _screenshotImageFormat.ToString().ToLower();

				// Find a name that isn't used (decorated by time)
				do
				{
					_screenshotFullName = Path.Combine( ApplicationPath, string.Format( "{0}[{1}]", _screenshotFileName, DateTime.Now.Ticks ) );

					if( Path.GetExtension( _screenshotFullName ) != extension )
						_screenshotFullName += extension;

				} while( File.Exists( _screenshotFullName ) );

				//switch( strFormatExtension )
				//{
				//case "jpeg":
				//   BitmapToJPEG( screenBitmap, strFilename, 80 );
				//   break;

				//default:
				screenBitmap.Save( _screenshotFullName, _screenshotImageFormat );
				//   break;
				//}

				_logToScreenshotOK = true;
			}
			catch( Exception )
			{
				_logToScreenshotOK = false;
			}
		}
		#endregion

		#region Outputs
		private void ExceptionToUI()
		{
			string msg = string.Format( "A {0} error occurred.  Your last action probably failed to finish.", _exceptionType );

			if( _logToScreenshotOK )
				msg += Environment.NewLine + Environment.NewLine + "A screen shot of the error condition was made.";

			if( _logToDatabaseOK || _logToEventLogOK || _logToFileOK )
				msg += Environment.NewLine + Environment.NewLine + "The error has been logged.";

			if( _killAppOnException )
				msg += Environment.NewLine + Environment.NewLine + "This application might exit.  If it does, you can restart it and try your task again.";

			if( _isConsoleApp )
			{
				Console.WriteLine( msg );
			}
			else
			{
				MessageBox.Show( msg, "Application Error" );
			}
		}
		#endregion

		#region String Conversions

		/// <summary>
		/// gather some system information that is helpful to diagnosing exception
		/// </summary>
		/// <returns></returns>
		protected override string SysInfoToString()
		{
			return SysInfoToString( false );
		}

		/// <summary>
		/// gather some system information that is helpful to diagnosing exception
		/// </summary>
		/// <param name="blnIncludeStackTrace"></param>
		/// <returns></returns>
		private string SysInfoToString( bool blnIncludeStackTrace )
		{
			StringBuilder builder = new StringBuilder();

			builder.Append( "Date and Time:         " );
			builder.Append( DateTime.Now );
			builder.Append( Environment.NewLine );

			builder.Append( "Machine Name:          " );
			try
			{
				builder.Append( Environment.MachineName );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "IP Address:            " );
			builder.Append( GetCurrentIP() );
			builder.Append( Environment.NewLine );

			builder.Append( "Current User:          " );
			builder.Append( ProcessIdentity() );
			builder.Append( Environment.NewLine );
			builder.Append( Environment.NewLine );

			builder.Append( "Application Domain:    " );
			try
			{
				builder.Append( System.AppDomain.CurrentDomain.FriendlyName );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}


			builder.Append( Environment.NewLine );
			builder.Append( "Assembly Codebase:     " );
			try
			{
				builder.Append( _parentAssembly.CodeBase );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "Assembly Full Name:    " );
			try
			{
				builder.Append( _parentAssembly.FullName );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "Assembly Version:      " );
			try
			{
				builder.Append( _parentAssembly.GetName().Version.ToString() );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );

			builder.Append( "Assembly Build Date:   " );
			try
			{
				builder.Append( GetAssemblyBuildDate( _parentAssembly, false ).ToString() );
			}
			catch( Exception e )
			{
				builder.Append( e.Message );
			}
			builder.Append( Environment.NewLine );
			builder.Append( Environment.NewLine );

			if( blnIncludeStackTrace )
				builder.Append( EnhancedStackTrace() );

			return builder.ToString();
		}


		/// <summary>
		/// get IP address of this machine
		/// </summary>
		/// <remarks>Not an ideal method for a number of reasons (guess why!) but the alternatives are very ugly</remarks>
		/// <returns></returns>
		private static string GetCurrentIP()
		{
			try
			{
				return System.Net.Dns.GetHostEntry( System.Net.Dns.GetHostName() ).AddressList[0].ToString();
			}
			catch( Exception )
			{
				return "127.0.0.1";
			}
		}
		#endregion
	}
}
